package com.kun.service.service.impl;

import org.activiti.api.process.model.ProcessDefinition;
import org.activiti.api.process.runtime.conf.ProcessRuntimeConfiguration;
import org.activiti.api.runtime.shared.UnprocessableEntityException;
import org.activiti.core.common.spring.security.policies.ProcessSecurityPoliciesManager;
import org.activiti.engine.ActivitiObjectNotFoundException;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.runtime.api.impl.ProcessRuntimeImpl;
import org.activiti.runtime.api.impl.ProcessVariablesPayloadValidator;
import org.activiti.runtime.api.model.impl.APIDeploymentConverter;
import org.activiti.runtime.api.model.impl.APIProcessDefinitionConverter;
import org.activiti.runtime.api.model.impl.APIProcessInstanceConverter;
import org.activiti.runtime.api.model.impl.APIVariableInstanceConverter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;

import java.util.Optional;

@Service
@PreAuthorize("hasRole('ACTIVITI_USER')")
public class RepairProcessRuntimeImpl extends ProcessRuntimeImpl {

    private final RepositoryService repositoryService;

    private final ProcessSecurityPoliciesManager securityPoliciesManager;

    private final APIProcessDefinitionConverter processDefinitionConverter;

    public RepairProcessRuntimeImpl(RepositoryService repositoryService,
                                    APIProcessDefinitionConverter processDefinitionConverter,
                                    RuntimeService runtimeService,
                                    ProcessSecurityPoliciesManager securityPoliciesManager,
                                    APIProcessInstanceConverter processInstanceConverter,
                                    APIVariableInstanceConverter variableInstanceConverter,
                                    APIDeploymentConverter deploymentConverter,
                                    ProcessRuntimeConfiguration configuration,
                                    ApplicationEventPublisher eventPublisher,
                                    ProcessVariablesPayloadValidator processVariablesValidator) {
        super(repositoryService, processDefinitionConverter, runtimeService, securityPoliciesManager, processInstanceConverter, variableInstanceConverter, deploymentConverter, configuration, eventPublisher, processVariablesValidator);
        this.repositoryService = repositoryService;
        this.securityPoliciesManager = securityPoliciesManager;
        this.processDefinitionConverter = processDefinitionConverter;
    }

    @Override
    public ProcessDefinition processDefinition(String processDefinitionId) {
        org.activiti.engine.repository.ProcessDefinition processDefinition;
        // try searching by Key if there is no matching by Id
        processDefinition = findLatestProcessDefinitionByKey(processDefinitionId)
                .orElseGet(() -> repositoryService.getProcessDefinition(processDefinitionId));

        checkProcessDefinitionBelongsToLatestDeployment(processDefinition);

        if (!securityPoliciesManager.canRead(processDefinition.getKey())) {
            throw new ActivitiObjectNotFoundException("Unable to find process definition for the given id:'" + processDefinitionId + "'");
        }
        return processDefinitionConverter.from(processDefinition);
    }

    private Optional<org.activiti.engine.repository.ProcessDefinition> findLatestProcessDefinitionByKey(String processDefinitionKey) {
        return repositoryService.createProcessDefinitionQuery()
                .processDefinitionKey(processDefinitionKey)
                .orderByProcessDefinitionAppVersion()
                .desc()
                .list()
                .stream()
                .findFirst();
    }

    private void checkProcessDefinitionBelongsToLatestDeployment(org.activiti.engine.repository.ProcessDefinition processDefinition){
        // 修复
        if (processDefinition.getAppVersion() != null && !selectLatestDeployment().getVersion().equals(processDefinition.getAppVersion())) {
            throw new UnprocessableEntityException("Process definition with the given id:'" + processDefinition.getId() + "' belongs to a different application version.");
        }
    }

}
